Wechat Mini | Note-4

微信小程序开发 Note-4

@ 2018年8月16日 16:23:30


数据库设计

背景音乐表(bgm)

评论表(comments)

搜索记录(search_records)

用户表(users)

关注(粉丝)表(users_fans)

用户点赞视频表(users_like_videos)

用户举报表(users_report)

视频表(videos)


项目初始化

先进行前端小程序页面的设计,修改工程目录结构,保留必备文件即可;

映入静态资源文件 resource;

将后台项目工程,采用分层的目录结构,方便工程的后期维护和管理;


Mybatis逆向生成工具

这个工具,是生成表的实体对象,实体mapper,以及实体.xml文件的工具;

我觉得这个工具生成的文件和使用的方式,过于相似SSM的开发过程,不推荐使用,由于课程的技术,无法修改

使用了SpringBoot开发框架的话,应该推荐使用JpaRepository的方式实现与数据持续层的访问;

在搭建分层工程结构的时候,遇到以下问题:

Q:JAR包冲突

A:删除对应JAR包、升级对应JAR包;

Q:由于是分层搭建工程,出现Caused by: java.lang.IllegalStateException: Found multiple @SpringBootConfiguration annotated classes [Generic bean: class [...]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null;这样的错误

A:由于是分层工程,并且是父子关系的pom继承关系,存在@SpringBootTest的class路径的自动查询出现问题,需要在每个子工程的@SpringBootTest中指明明确的class路径;

例如

1
2
3
4
5
6
7
8
9
10
11
@RunWith(SpringRunner.class)
@SpringBootTest
public class VideosDevServiceApplicationTests {
...
}
// 修改为
@RunWith(SpringRunner.class)
@SpringBootTest(classes = VideosDevServiceApplication.class)
public class VideosDevServiceApplicationTests {
...
}

用户注册接口

第一,我为上面说这是一个类似SSM框架不推荐的话,掌嘴;

第二,使用插件(idworker);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// RegisterLoginController
@RestController
public class RegisterLoginController {
@Autowired
private UserService userService;

@PostMapping("/regist")
public IMoocJSONResult regist(@RequestBody Users user) throws Exception {
// 1.判断用户名和密码不为空
if (StringUtils.isBlank(user.getUsername()) || StringUtils.isBlank(user.getPassword())) {
return IMoocJSONResult.errorMsg("用户名和密码不能为空");
}
// 2.判断用户是否存在
boolean usernameIsExist = userService.queryUsernameIsExist(user.getUsername());
if(!usernameIsExist){
user.setNickname(user.getUsername());
user.setPassword(MD5Utils.getMD5Str(user.getPassword()));
user.setFansCounts(0);
user.setReceiveLikeCounts(0);
user.setFollowCounts(0);
userService.saveUser(user);
}else{
return IMoocJSONResult.errorMsg("用户名已存在");
}
// 3.保存用户,注册信息
return IMoocJSONResult.ok();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// UserServiceImpl
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UsersMapper usersMapper;
@Autowired
private Sid sid;

@Override
@Transactional(propagation = Propagation.SUPPORTS)
public boolean queryUsernameIsExist(String username) {
Users user = new Users();
user.setUsername(username);
Users result = usersMapper.selectOne(user);
return result != null;
}

@Override
@Transactional(propagation = Propagation.REQUIRED)
public void saveUser(Users user) {
user.setId(sid.nextShort());
usersMapper.insert(user);
}
}

Swagger2使用与restful接口测试

可以生成文档形式的api,并提供给不同的团队;

便于自我测试,也便于查阅;

无须过多冗余的word文档;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
@EnableSwagger2
public class Swagger2 {
/**
* swagger2的配置文件,扫描的包等
* @return
*/
@Bean
public Docket createResApi(){
return new Docket(DocumentationType.SWAGGER_2).apiInfo(apiInfo()).select()
.apis(RequestHandlerSelectors.basePackage("top.rex.videos.controller"))
.paths(PathSelectors.any()).build();
}

/**
* 构建API文档的信息
* @return
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("Swagger API Document")
.contact(new Contact("REX", "https://reeeexchen.github.io/", "dy708484@163.com"))
.description("This Is About The Description")
.version("1.0.0").build();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 对于Controller的API注释
@RestController
@Api(value = "User Login & Register",tags = {"Login & Register Controller"})
public class RegisterLoginController {
@ApiOperation(value = "Register",notes = "Register")
@PostMapping("/regist")
public IMoocJSONResult regist(@RequestBody Users user) throws Exception {
...
}
}
// 对于Users实体的API注释
@ApiModel(value="Users Entity", description="This Is Users Obj")
public class Users {

@ApiModelProperty(hidden=true)
@Id
private String id;

@ApiModelProperty(value="Username", name="username", example="imoocuser", required=true)
private String username;

@ApiModelProperty(value="Password", name="password", example="123456", required=true)
private String password;
}

@太心累了,用Mybatis配置扫描包的操作,分层工程结构下的包路径,总是出错,无从下手;

@2018年8月17日 13:36:47

从昨晚开始,启动之后做测试,一直报出一个错误

[java.lang.IncompatibleClassChangeError: Implementing class]

我这个错误有点特殊,跟网上能找到的不一样,弄了4个小时之后,终于解决;

主要的问题依然是jar包;由于之前尝试运行一个含有druid的项目,一直也是类似的错误;

这次对机器上的JAVA进行了升级;

JRE和JDK从1.8.0_111升级到了1.8.0_181(JAVA1.8最新版本),问题解决;


用户注册联调

配置流程

服务器域名请在 小程序后台-设置-开发设置-服务器域名 中进行配置,配置时需要注意:

  • 域名只支持 https (requestuploadFiledownloadFile) 和 wss (connectSocket) 协议;
  • 域名不能使用 IP 地址或 localhost
  • 域名必须经过 ICP 备案;
  • 出于安全考虑,api.weixin.qq.com 不能被配置为服务器域名,相关API也不能在小程序内调用。开发者应将 appsecret 保存到后台服务器中,通过服务器使用 appsecret 获取 accesstoken,并调用相关 API。
  • 对于每个接口,分别可以配置最多 20 个域名

注意将微信开发工具中的,不进行证书校验打开

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
const app = getApp()
Page({
data: {
},
doRegist:function(e){
var formObject = e.detail.value;
var username = formObject.username;
var password = formObject.password;
// 简单的前端验证
if(username.length == 0 || password.length == 0){
wx.showToast({
title: '用户名或密码不能为空',
icon:'none',
duration:3000
})
}else{
var serverUrl = app.serverUrl;
wx.showLoading({
title: '提交中',
});
wx.request({
url: serverUrl + '/regist',
method:"POST",
data:{
username:username,
password:password
},
header:{
'content-type':'application/json'
},
success:function(res){
console.log(res.data);
wx.hideLoading();
var status = res.data.status;
if(status == 200){
wx.showToast({
title: '用户注册成功',
icon: 'success',
duration: 3000
}),
app.userInfo = res.data.data;
}else if(status == 500){
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 3000
})
}
}
})
}
}
})

用户登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class UserServiceImpl implements UserService {

@Autowired
private UsersMapper userMapper;

@Transactional(propagation = Propagation.SUPPORTS)
@Override
public Users queryUserForLogin(String username, String password) {
Example userExample = new Example(Users.class);
Example.Criteria criteria = userExample.createCriteria();
criteria.andEqualTo("username",username);
criteria.andEqualTo("password",password);
Users result = userMapper.selectOneByExample(userExample);
return result;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@RestController
@Api(value = "LOGIN & REGISTER API", tags = {"LOGIN & REGISTER CONTROLLER"})
public class RegistLoginController {
@Autowired
private UserService userService;

@ApiOperation(value = "LOGIN", notes = "LOGIN API")
@PostMapping("/login")
public IMoocJSONResult login(@RequestBody Users user) throws Exception {
String username = user.getUsername();
String password = user.getPassword();
// 1.判断用户名和密码不为空
if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
return IMoocJSONResult.errorMsg("用户名和密码不能为空");
}
// 2.判断用户是否存在
Users userResult = userService.queryUserForLogin(username,MD5Utils.getMD5Str(password));
// 3.返回用户信息
if(userResult != null){
userResult.setPassword("");
return IMoocJSONResult.ok(userResult);
}else{
return IMoocJSONResult.errorMsg("用户名或密码错误,请重试");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const app = getApp()
Page({
data: {
},
// 登录
doLogin: function(e) {
var me = this;
var formObject = e.detail.value;
var username = formObject.username;
var password = formObject.password;
// 简单的前端验证
if (username.length == 0 || password.length == 0) {
wx.showToast({
title: '用户名或密码不能为空',
icon: 'none',
duration: 3000
});
} else {
var serverUrl = app.serverUrl;
wx.showLoading({
title: '登录中',
});
// BackEnd
wx.request({
url: serverUrl + '/login',
method: "POST",
data: {
username: username,
password: password
},
header: {
'content-type': 'application/json'
},
success: function(res) {
console.log(res.data);
wx.hideLoading();
var status = res.data.status;
if (status == 200) {
wx.showToast({
title: '登录成功',
icon: 'success',
duration: 2000
});
app.userInfo = res.data.data;
// TODO 页面跳转
} else if (status == 500) {
wx.showToast({
title: res.data.msg,
icon: 'none',
duration: 3000
});
}
}
})
}
}
})

有状态会话与无状态会话基本概念

有状态session:每个用户访问都会产生一个session;

无状态session:app的用户访问服务时,不产生session;

redis-session:以通过无状态session,用redis的方式维护一个持续的会话状态;用户访问服务时,将用户的信息以JSON的形式保存到redis的缓存中;

redis-session好处

用户信息存储到redis缓存中,形成无状态会话;

便于扩展,当单体应用该扩展成集群会更方便;

便于权限认证;


redis

之前已经在我的阿里云服务器中搭建过redis-server,在此不再记录,详细参考网上的搭建教程;

基本命令

1
2
3
4
redis-cli.exe -h xxx.xxx.xxx.xxx -p 6379 -a xxx
ping
set key value
get key

开发用户redis-session

1
2
3
4
5
6
7
@RestController
public class BasicController {
@Autowired
public RedisOperator redis;

public static final String USER_REDIS_SESSION = "user-redis-session";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@RestController
@Api(value = "LOGIN & REGISTER API", tags = {"LOGIN & REGISTER CONTROLLER"})
public class RegistLoginController extends BasicController{

@Autowired
private UserService userService;

/**
* 设置用户登录与注册的redis-session-token
* @param userModel
* @return
*/
public UsersVO setUserRedisSessionToken(Users userModel){
// REDIS SESSION
String uniqueToken = UUID.randomUUID().toString();
redis.set(USER_REDIS_SESSION + ":" + userModel.getId(),uniqueToken,1000 * 60 * 30);

UsersVO usersVO = new UsersVO();
BeanUtils.copyProperties(userModel,usersVO);
usersVO.setUserToken(uniqueToken);
return usersVO;
}
}

@ 2018年8月17日 15:15:10